package main

import (
	"bufio"
	"fmt"
	"io"
	"net"
	"net/http"
	"os"
	"regexp"
	"runtime"
	"strings"
	"time"
)

/**
本程序为内网环境中web服务器所在的主机（或者可连接到内网web服务器同时可以访问外网的间机器）
工作流程：
	1.连接远端服务器ControlAddrPort，接受远端服务器的控制命令
	2.当用户访问远端服务器时，ControlAddrPort传来控制命令"new\n"，
	3.执行combine方法，程序同时拨通TunnelAddrPort 和 ServerAddrPort，
	4.并通过joinConn方法，用io.Copy的方式讲TunnelAddrPort的通信数据和ServerAddrPort的通信数据配对，
实现将内网数据提交到中介服务器ControlAddrPort，再通过中介服务期上的程序实现内网穿透
*/

var outSideConn *net.TCPConn

var (
	//中介服务器控制端程序连接地址和端口
	ControlAddrPort = "49.232.218.242:8009"
	//链路实际通信连接地址和端口
	TunnelAddrPort = "49.232.218.242:8008"
	//内网服务程序地址和端口
	ServerAddrPort = "192.168.7.3:666"
)

func main() {
	//加载配置文件
	loadConfig()
	println("ControlAddrPort", ControlAddrPort)
	println("TunnelAddrPort", TunnelAddrPort)
	println("ServerAddrPort", ServerAddrPort)
	//内存检测
	go checkMem()
	//保活
	go keepAlive()
	//开始监听
	for {
		//放在for 循环中，断线重新连接
		connectControl()
	}
}

func loadConfig() {
	content, err := os.ReadFile("./conf.conf")
	if err != nil {
		return
	}
	mp := regexp.MustCompile(`(.+)?=[0-9:\.]+`)
	matches := mp.FindAll(content, -1)
	for _, match := range matches {
		if strings.HasPrefix(string(match), "ControlAddrPort=") {
			ControlAddrPort = string(match)[len("ControlAddrPort="):]
		}
		if strings.HasPrefix(string(match), "TunnelAddrPort=") {
			TunnelAddrPort = string(match)[len("TunnelAddrPort="):]
		}
		if strings.HasPrefix(string(match), "ServerAddrPort=") {
			ServerAddrPort = string(match)[len("ServerAddrPort="):]
		}

	}
}

// 连接到服务器的8009控制端口，随时接受服务器的控制请求，随时待命
func connectControl() {
	var tcpAddr *net.TCPAddr
	//TCP连接位于外网主机上的ControlPort端口，默认8009
	//当用户访问外网主机时，外网通过此链接向内网发送控制请求"new\n"
	tcpAddr, _ = net.ResolveTCPAddr("tcp", ControlAddrPort)
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	if err != nil {
		fmt.Println("Client connect error ! " + err.Error())
		return
	}
	outSideConn = conn
	fmt.Println(outSideConn.LocalAddr().String() + " : Client connected!8009")

	for {
		reader := bufio.NewReader(outSideConn)
		s, err := reader.ReadString('\n')
		if err != nil || err == io.EOF {
			if err != nil {
				println("连接关闭", err.Error())
			}
			if err == io.EOF {
				println("连接关闭EOF")
			}
			outSideConn.Close()
			fmt.Printf("outSideConn关闭")
			break
		} else {
			//接收到new的指令的时候，新建一个tcp连接
			switch s {
			case "new\n":
				go combine()
				fmt.Println("new")
			case "hi\n":
				//忽略掉hi的请求
				fmt.Println("server say hi")
			default:
				fmt.Println("unknown command:" + s)
			}
			//if s == "new\n" {
			//	go combine()
			//}
			//if s == "hi" {
			//	//忽略掉hi的请求
			//	fmt.Println("server say hi")
			//}
		}

	}
}

func keepAlive() {
	for {
		time.Sleep(time.Minute)
		if outSideConn != nil {
			_, err := outSideConn.Write([]byte("hi\n"))
			if err != nil {
				outSideConn.Close()
				tcpAddr, _ := net.ResolveTCPAddr("tcp", ControlAddrPort)
				outSideConn, err = net.DialTCP("tcp", nil, tcpAddr)
				if err != nil {
					fmt.Println("Client connect error ! " + err.Error())
					return
				}
			}
		}
	}
}

// combine方法的代码，整合local和remote的tcp连接
func combine() {
	local := connectLocal()
	remote := connectRemote()
	if local != nil && remote != nil {
		joinConn(local, remote)
	} else {
		if local != nil {
			err := local.Close()
			if err != nil {
				fmt.Println("close local:" + err.Error())
			}
		}
		if remote != nil {
			err := remote.Close()
			if err != nil {
				fmt.Println("close remote:" + err.Error())
			}

		}
	}
}
func joinConn(local *net.TCPConn, remote *net.TCPConn) {
	f := func(local *net.TCPConn, remote *net.TCPConn) {
		_, err := io.Copy(local, remote)
		if err != nil {
			fmt.Println("io.Copy", err.Error())
			return
		}
	}
	go f(local, remote)
	go f(remote, local)
}

// connectLocal 连接到内网web服务器！
func connectLocal() *net.TCPConn {
	var tcpAddr *net.TCPAddr
	tcpAddr, _ = net.ResolveTCPAddr("tcp", ServerAddrPort)
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	if err != nil {
		fmt.Println("Client connect error ! " + err.Error())
		return nil
	}
	conn.SetKeepAlive(true)
	conn.SetDeadline(time.Now().Add(time.Minute * 10))
	fmt.Println(conn.LocalAddr().String() + " : Client connected!8000")
	return conn

}

// connectRemote 连接到服务端的8008端口！
func connectRemote() *net.TCPConn {
	var tcpAddr *net.TCPAddr
	tcpAddr, _ = net.ResolveTCPAddr("tcp", TunnelAddrPort)
	conn, err := net.DialTCP("tcp", nil, tcpAddr)
	if err != nil {
		fmt.Println("Client connect error ! " + err.Error())
		return nil
	}
	conn.SetKeepAlive(true)
	conn.SetDeadline(time.Now().Add(time.Minute * 10))
	fmt.Println(conn.LocalAddr().String() + " : Client connected!8008")
	return conn
}

func checkMem() {
	for {
		var m runtime.MemStats
		runtime.ReadMemStats(&m)
		fmt.Println("Memory Allocation:", m.Alloc, "\tTotal Allocation:", m.TotalAlloc, "\tSys:", m.Sys, "\tHeap Allocation:", m.HeapAlloc, "\tHeap System:", m.HeapSys)
		//手动清理缓存垃圾
		runtime.GC()
		time.Sleep(time.Second * 100)
	}
}

func debug() {
	http.ListenAndServe("localhost:789", nil)
}
